using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;

namespace Microsoft.Cryptography.Diagnostics
{
    /// <summary>
    /// Default LogProvider
    /// </summary>
    public class DefaultLogMessage : ILogMessage
    {
        #region ILogMessage Members

        /// <summary>
        /// Logs a message, using the default .Net class System.Diagnostic.TraceSource
        /// </summary>
        /// <param name="priority">priority for the message, which should be logged</param>
        /// <param name="sources">log sources for the message, which should be logged</param>
        /// <param name="message">message text, which should be logged</param>
        /// <param name="parameters">parameters for the message, which should be logged</param>
        public void LogMessage(LogProvider.LogPriorities priority, 
            LogProvider.LogSources[] sources, 
            string message, 
            params object[] parameters)
        {
            if (sources == null)
            {
                throw new ArgumentNullException("sources");
            }

            List<LogProvider.LogSources> logSources = new List<LogProvider.LogSources>(sources);

            // If the priority is 'Error' ensure, that the message is traced in the 'CRYPTO_Errors' trace-source
            if (priority == LogProvider.LogPriorities.Error &&
                Array.IndexOf<LogProvider.LogSources>(sources, LogProvider.LogSources.CRYPTO_Errors) == -1)
            {
                logSources.Add(LogProvider.LogSources.CRYPTO_Errors);
            }

            // Mapping the priority to TraceEventType
            TraceEventType severity;
            switch (priority)
            {
                case LogProvider.LogPriorities.Debug:
                    severity = TraceEventType.Verbose;
                    break;
                case LogProvider.LogPriorities.Info:
                    severity = TraceEventType.Information;
                    break;
                case LogProvider.LogPriorities.Warning:
                    severity = TraceEventType.Warning;
                    break;
                case LogProvider.LogPriorities.Error:
                    severity = TraceEventType.Error;
                    break;
                default:
                    severity = TraceEventType.Verbose;
                    break;
            }

            // Formatting the message text
            string messageText = string.Empty;
            try
            {
                if (parameters != null &&
                    parameters.Length > 0)
                {

                    messageText += string.Format(message, parameters);
                }
                else
                {
                    messageText += message;
                }
            }
            catch (Exception err)
            {
                string[] paramStrings = new string[parameters.Length];

                for (int i = 0; i < parameters.Length; i++)
                {
                    if (parameters[i] == null)
                    {
                        paramStrings[i] = "<NULL>";
                    }
                    else
                    {
                        paramStrings[i] = parameters[i].ToString();
                    }
                }

                messageText = string.Format("Could not format message '{0}' with Parameters '{1}'. Exception: {2}",
                    message,
                    string.Join(", ", paramStrings),
                    err);

                severity = TraceEventType.Error;
            }

#if DEBUG_OUTPUT
            DateTime now = DateTime.Now;
            Debug.WriteLine(string.Format("{0}.{1} {2} {3}",
                now.ToString("yyyy-MM-dd hh:mm:ss"),
                now.Millisecond,
                priority.ToString()[0],
                messageText));
#endif //DEBUG_OUTPUT

            foreach (LogProvider.LogSources logSource in logSources)
            {
                TraceSource source = GetTraceSource(logSource.ToString());

                source.TraceEvent(severity,
                    0,
                    messageText);
            }
        }

        #endregion

        private static System.Collections.Hashtable _sources = new System.Collections.Hashtable();

        private static TraceSource GetTraceSource(string name)
        {
            string key = string.Format("{0}@{1}",
                name,
                System.Threading.Thread.CurrentThread.ManagedThreadId);

            if (_sources[key] == null)
            {
                lock (_sources)
                {
                    if (_sources[key] == null)
                    {
                        TraceSource source = new TraceSource(name);
                        _sources[key] = source;

                        return source;
                    }
                }
            }

            return (TraceSource)_sources[key];
        }
    }
}
